// SPDX-FileCopyrightText: 2023-2025 CASTest Corporation Limited
// SPDX-FileCopyrightText: 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences
// SPDX-License-Identifier: LGPL-v3

#include "atpg/ATPGEngine.h"

namespace ictest {

/**
 * @brief
 * atpg engine
 * @details
 * generate test pattern for every fault
 */
void ATPGEngine::GenerateTestPatternForEveryFaultInList() {
  int abort_limit = global_option_->abort_limit_;
  int pattern_per_sim = global_option_->pattern_per_sim_;
  LOG_ASSERT(pattern_per_sim >= 1 && pattern_per_sim <= 64,
             "Illegal Simulated Patterns");

  int atpg_status = NO_TEST;
  int num_curr_pattern = 0;
  int num_total_pattern = 0;
  auto &uncollapsed_saf_list = saf_list_->GetUncollapsedSAFs();
  std::vector<SAF *> rest_faults;
  for (auto fault : uncollapsed_saf_list) {
    if (fault->GetSAFStatus() == SAFStatus::UC) {
      rest_faults.emplace_back(fault);
    }
  }
  int num_rest_faults = (int)rest_faults.size();

  std::vector<std::vector<std::vector<int>>> atpg_pi_pats;
  std::vector<std::vector<std::vector<int>>> atpg_sc_pats;
  std::vector<std::vector<int>> atpg_pi_pat;
  std::vector<std::vector<int>> atpg_sc_pat;
  std::vector<Pattern> patterns;

  PatternParser pp;
  pp.SetupPrimNetlist(prim_);
  pp.SetPatternType(PatternType::BASIC_SCAN_PT);

  LOG_INFO("{} ...", "ATPG Start")
  LOG_INFO("{:^15}{:^15}{:^15}", "total_pat_nums", "detected", "coverage")

  auto start = std::chrono::steady_clock::now();

  auto *saf_test_generator = new SAFTest(prim_, sim_);
  saf_test_generator->InitSAFTestStatus();

  for (int f_id = 0; f_id < num_rest_faults; f_id++) {
    int rand_idx = rand() % (num_rest_faults - f_id) + f_id;
    std::swap(rest_faults[f_id], rest_faults[rand_idx]);
    auto *target_fault = rest_faults[f_id];
    if (target_fault->GetSAFStatus() != SAFStatus::UC) {
      continue;
    }

    atpg_status = saf_test_generator->TestGen(target_fault, abort_limit);

    if (atpg_status == OVER_BACKTRACK) {
      target_fault->SetSAFStatus(SAFStatus::ATPG_ABORT);
      continue;
    }
    if (atpg_status == NO_TEST) {
      target_fault->SetSAFStatus(SAFStatus::RE);
      continue;
    }

    assert(atpg_status == TEST_FOUND);

    GeneratePIPatForSA(saf_test_generator, atpg_pi_pat);
    GenerateSCPatForSA(saf_test_generator, atpg_sc_pat);
    atpg_pi_pats.emplace_back(atpg_pi_pat);
    atpg_sc_pats.emplace_back(atpg_sc_pat);

    num_curr_pattern = static_cast<int>(atpg_pi_pats.size());
    if (num_curr_pattern == pattern_per_sim) {
      num_total_pattern += num_curr_pattern;
      GenerateFinalSCPatternForSimulation(atpg_sc_pats);
      pp.LoadInternalFullScanPatternForSAF(atpg_pi_pats, atpg_sc_pats, patterns,
                                           true);
      sim_->RunSAFSimulation(patterns, uncollapsed_saf_list, true);
      atpg_pi_pats.clear();
      atpg_sc_pats.clear();
      patterns.clear();
    }
  }
  num_curr_pattern = static_cast<int>(atpg_pi_pats.size());
  if (num_curr_pattern) {
    num_total_pattern += num_curr_pattern;
    GenerateFinalSCPatternForSimulation(atpg_sc_pats);
    pp.LoadInternalFullScanPatternForSAF(atpg_pi_pats, atpg_sc_pats, patterns,
                                         true);
    sim_->RunSAFSimulation(patterns, rest_faults, true);
    atpg_pi_pats.clear();
    atpg_sc_pats.clear();
    patterns.clear();
  }
  auto end = std::chrono::steady_clock::now();
  auto duration = std::chrono::duration<double>(end - start);
  ReportSAFaultStatistics(uncollapsed_saf_list,
                          "Uncollapsed Stuck-at Fault Summary Report");

  DumpSAFList();
  LOG_INFO("#Pattern : {}", num_total_pattern)
  LOG_INFO("RunTime  : {}", fmt::format("{:.3f} s", duration.count()))
  LOG_INFO("{} ...", "ATPG End")
  LOG_INFO("")
}
/**
 * @brief
 * dump fault list
 * @param report
 */
void ATPGEngine::DumpSAFList() {
  std::ofstream dump_ofs;
  //  RemoveDir(dump_path_);
  if (access(dump_path_.c_str(), F_OK) == -1) {
    mkdir(dump_path_.c_str(), S_IRWXO | S_IRWXG | S_IRWXU);
  }
  std::string saf_dump_path = dump_path_ + "/ictest_atpg.flist";
  dump_ofs.open(saf_dump_path);
  if (!dump_ofs.is_open()) {
    LOG_ASSERT(false, "open Dump " + saf_dump_path + " fail")
  }

  for (auto fault : saf_list_->GetUncollapsedSAFs()) {
    if (fault->GetSAFType() == SAFType::SA0) {
      dump_ofs << "sa0"
               << "  ";
    } else {
      dump_ofs << "sa1"
               << "  ";
    }

    switch (fault->GetSAFStatus()) {
    case SAFStatus::DS:
      dump_ofs << "DS"
               << "  ";
      break;
    case SAFStatus::UC:
      dump_ofs << "UC"
               << "  ";
      break;
    case SAFStatus::UO:
      dump_ofs << "UO"
               << "  ";
      break;
    case SAFStatus::ATPG_ABORT:
      dump_ofs << "AB"
               << "  ";
      break;
    case SAFStatus::RE:
      dump_ofs << "RE"
               << "  ";
      break;
    case SAFStatus::TI:
      dump_ofs << "TI"
               << "  ";
      break;
    case SAFStatus::BL:
      dump_ofs << "BL"
               << "  ";
      break;
    case SAFStatus::UU:
      dump_ofs << "UU"
               << "  ";
      break;
    case SAFStatus::AU:
      dump_ofs << "AU"
               << "  ";
      break;
    case SAFStatus::DI:
      dump_ofs << "DI"
               << "  ";
      break;
    case SAFStatus::PT:
      dump_ofs << "PT"
               << "  ";
      break;
    case SAFStatus::DT_CHAIN_TEST:
      dump_ofs << "CT"
               << "  ";
      break;
    case SAFStatus::CONFLICT_FREE:
      dump_ofs << "CF"
               << "  ";
      break;
    default:
      LOG_ERROR("no such fault status");
    }

    auto fault_name = fault->GetSAFName();
    if (fault_name.find('=') != -1) {
      fault_name.erase(0, 2);
      int remove_idx = fault_name.find('=');
      fault_name.erase(remove_idx, 2);
      fault_name.insert(remove_idx, 1, '/');
    }
    dump_ofs << fault_name << std::endl;
  }
  dump_ofs.close();
}

/**
 * @brief
 * report fault statistics info
 * @param saflist fault list
 * @param meg report messages
 */
void ATPGEngine::ReportSAFaultStatistics(std::vector<SAF *> &saflist,
                                         const std::string &meg) {
  int detect = 0, ds = 0, di = 0, dt_chain_test = 0;
  int possible = 0, pt = 0, pu = 0;
  int atpg_untestable = 0, au = 0;
  int abort = 0, ab = 0, uc = 0, uo = 0;
  int untestable = 0, uu = 0, ti = 0, bl = 0, re = 0;

  for (auto fault : saflist) {
    switch (fault->GetSAFStatus()) {
    case SAFStatus::DS: {
      ds++;
      detect++;
    } break;
    case SAFStatus::DI: {
      di++;
      detect++;
    } break;
    case SAFStatus::DT_CHAIN_TEST: {
      dt_chain_test++;
      detect++;
    } break;
    case SAFStatus::PT: {
      pt++;
      possible++;
    } break;
    case SAFStatus::PU: {
      pu++;
      possible++;
    } break;
    case SAFStatus::AU: {
      au++;
      atpg_untestable++;
    } break;
    case SAFStatus::ATPG_ABORT: {
      ab++;
      abort++;
    } break;
    case SAFStatus::UC: {
      uc++;
      abort++;
    } break;
    case SAFStatus::UO: {
      uo++;
      abort++;
    } break;
    case SAFStatus::UU: {
      uu++;
      untestable++;
    } break;
    case SAFStatus::TI: {
      ti++;
      untestable++;
    } break;
    case SAFStatus::BL: {
      bl++;
      untestable++;
    } break;
    case SAFStatus::RE: {
      re++;
      untestable++;
    } break;
    default:
      LOG_INFO("unknown fault classification {}", (int)fault->GetSAFStatus())
      assert(false);
    }
  }
  LOG_INFO("")
  LOG_INFO("{:^47}", meg)
  LOG_INFO("-----------------------------------------------")
  LOG_INFO("{:<30}{:^8}{:>8}", "fault class", "code", "#faults")
  LOG_INFO("{:<30}{:^8}{:>8}", "------------------------------", "----",
           "---------")
  LOG_INFO("{:<30}{:^8}{:>8}", "Detected", "DT", detect)
  LOG_INFO("{:<30}{:^8}{:>8}", "Possibly detected", "PT", pt)
  LOG_INFO("{:<30}{:^8}{:>8}", "Possibly undetected", "PU", pu)
  LOG_INFO("{:<30}{:^8}{:>8}", "Undetectable", "UD", untestable)
  LOG_INFO("{:<30}{:^8}{:>8}", "ATPG untestable", "AU", atpg_untestable)
  LOG_INFO("{:<30}{:^8}{:>8}", "Not detected", "AB", abort)
  LOG_INFO("-----------------------------------------------")

  int num_fault = (int)saflist.size();
  double fault_coverage = 1.0 * detect / num_fault * 100;
  double test_coverage = 1.0 * (detect + untestable) / num_fault * 100;

  LOG_INFO("{:<30}{:>16}", "total faults", num_fault)
  LOG_INFO("{:<30}{:>16}", "fault coverage",
           fmt::format("{:.3f}%", fault_coverage))
  LOG_INFO("{:<30}{:>16}", "test coverage",
           fmt::format("{:.3f}%", test_coverage))
  LOG_INFO("-----------------------------------------------")
  LOG_INFO("")
}
/**
 * @brief
 * generate ppi pattern according to good machine
 * @param saf_test_generator
 * @param atpg_sc_pat
 */
void ATPGEngine::GenerateSCPatForSA(
    SAFTest *saf_test_generator, std::vector<std::vector<int>> &atpg_sc_pat) {
  atpg_sc_pat.resize(prim_->ScanChains().size());
  for (int i = 0; i < prim_->ScanChains().size(); ++i) {
    int chain_length = (int)prim_->ScanChains()[i]->GetChainLength();
    atpg_sc_pat[i].assign(chain_length, LOGIC_x);
    for (int j = 0; j < chain_length; ++j) {
      auto pi_id = prim_->ScanChains()[i]->ScanChainDFFs()[j]->GetGId();
      auto pi_bit = saf_test_generator->good_[pi_id];
      if (pi_bit != LOGIC_0 && pi_bit != LOGIC_1 && pi_bit != LOGIC_x) {
        assert(false);
      }
      atpg_sc_pat[i][j] = pi_bit;
    }
  }
}

/**
 * @brief
 * generate pi pattern according to good machine
 * @param saf_test_generator
 * @param atpg_pi_pat
 */
void ATPGEngine::GeneratePIPatForSA(
    SAFTest *saf_test_generator, std::vector<std::vector<int>> &atpg_pi_pat) {

  // 获取pi向量    
  atpg_pi_pat.resize(prim_->NumPIs(), std::vector<int>(3, LOGIC_x));
  for (int p_id = 0; p_id < prim_->GetPIGates().size(); p_id++) {
    auto pi_gate = prim_->GetPIGates()[p_id];
    auto pi_bit = saf_test_generator->good_[pi_gate->GetGId()];

    if (pi_bit != LOGIC_0 && pi_bit != LOGIC_1 && pi_bit != LOGIC_x) {
      assert(false);
    }
    if (prim_->GetClkOffState().count(pi_gate->GetGId())) {
      if (pi_bit == (int)prim_->GetClkOffState()[pi_gate->GetGId()]) {
        atpg_pi_pat[p_id][0] = pi_bit;
        atpg_pi_pat[p_id][1] = pi_bit;
        atpg_pi_pat[p_id][2] = pi_bit;
      } else if (pi_bit ==
                 1 - (int)prim_->GetClkOffState()[pi_gate->GetGId()]) {
        atpg_pi_pat[p_id][0] = 1 - pi_bit;
        atpg_pi_pat[p_id][1] = pi_bit;
        atpg_pi_pat[p_id][2] = 1 - pi_bit;
      } else {
        pi_bit = (int)prim_->GetClkOffState()[pi_gate->GetGId()];
        atpg_pi_pat[p_id][0] = pi_bit;
        atpg_pi_pat[p_id][1] = pi_bit;
        atpg_pi_pat[p_id][2] = pi_bit;
      }
    } else {
      atpg_pi_pat[p_id][0] = pi_bit;
      atpg_pi_pat[p_id][1] = pi_bit;
      atpg_pi_pat[p_id][1] = pi_bit;
      atpg_pi_pat[p_id][2] = pi_bit;
    }
  }
}

/**
 * @brief
 * fix ppi pattern for simulator
 * @param atpg_sc_pats ppi pattern
 */
void ATPGEngine::GenerateFinalSCPatternForSimulation(
    std::vector<std::vector<std::vector<int>>> &atpg_sc_pats) {
  // 获取扫描单元向量
  for (auto &sc_pat : atpg_sc_pats) {
    for (int i = 0; i < prim_->ScanChains().size(); ++i) {
      for (int j = 0; j < prim_->ScanChains()[i]->ScanChainDFFs().size(); ++j) {
        int sc_id = prim_->ScanChains()[i]->ScanChainDFFs()[j]->GetGId();
        if (prim_->GetDffInvFlag()[sc_id]) {
          sc_pat[i][j] = not_table_five[sc_pat[i][j]];
        }
      }
    }
  }
}

} // namespace ictest
